/*
 * Copyright (c) 2024-2025 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <tools/list.h>
#include <tools/macros.h>
#include <core/endpoint.h>
#include <core/fault.h>
#include <core/schedule.h>
#include <core/task.h>
#include <uapi/errors.h>
#include <uapi/ipcbuffer.h>

void endpoint_init(endpoint_t *ep)
{
    list_init(&ep->waitlist);
    ep->state = EP_IDLE;
    spin_init_unlocked(&ep->lock);
}

static void endpoint_enque(endpoint_t *ep, tcb_t *task)
{
    uint32_t         core_id = task_get_coreid(task);
    schedule_data_t *_sd     = get_schedule_data(core_id);

    schedule_attach_block(task, ep, RESOURCE_ENDPONIT);
    set_schedule_action(_sd, ACTION_CHOOSE_NEW);
}

static inline void endpoint_deque(tcb_t *task)
{
    schedule_detach_block(task);
}

static inline void try_promote_priority(tcb_t *dst, tcb_t *src)
{
    if (task_get_priority(src) < task_get_priority(dst)) {
        schedule_change_task_priority(dst, task_get_priority(src));
    }
}

static inline void recovery_priority(tcb_t *tsk)
{
    task_set_priority(tsk, task_get_orig_priority(tsk));
}

void endpoint_send(endpoint_t *ep, uint64_t tag, uint64_t m0, uint64_t m1, void *_sender)
{
    resource_t *receiver_res = NULL;
    tcb_t      *receiver     = NULL;
    tcb_t      *sender       = _sender;
    switch (ep->state) {
        case EP_SEND:
        case EP_IDLE:
            if (msgtag_get_extra(tag) != 0) {    // non-block send
                task_set_retcode(sender, EAGAIN);
            } else {
                ipcbuffer_save(task_get_ipcbuffer(sender), tag, m0, m1);
                endpoint_enque(ep, sender);
                ep->state = EP_SEND;
            }
            break;
        case EP_RECV:
            receiver_res = list_first_entry(&ep->waitlist, resource_t, s_list);
            receiver     = receiver_res->s_owner;
            endpoint_deque(receiver);
            if (list_is_empty(&ep->waitlist)) {
                ep->state = EP_IDLE;
            }
            // transfer the messages and schedule the receiver
            if (task_get_aid(receiver) == 0L) {
                task_get_ipcbuffer(receiver)->ep_sender_aid  = task_get_aid(sender);
                task_get_ipcbuffer(receiver)->ep_sender_cptr = sender->cptr;
            }
            ipcbuffer_regs_transfer(receiver, tag, m0, m1);
            ipcbuffer_partial_transfer(receiver, sender, tag);
            if (task_get_flags(sender) & TASK_ACT_CALL) {
                endpoint_get(ep);
                // the sender want to do a call, then save the caller info to the receiver
                sender->calledep = ep;
                list_insert(&sender->callernode, &receiver->callers);
                task_set_state(sender, TASK_BLOCKED);
                schedule_detach(sender);    // set_schedule_action(_sd, ACTION_CHOOSE_NEW);
                task_clear_flags(sender, TASK_ACT_CALL);

                try_promote_priority(receiver, sender);
            } else {
                task_set_retcode(sender, ESUCCESS);
            }

            task_set_retcode(receiver, ESUCCESS);
            schedule_attach_ready(receiver);
            break;
        default:    // these codes should never be executed
            task_set_retcode(sender, EINVAL);
            break;
    }
}

void endpoint_receive(endpoint_t *ep, uint64_t tag, void *_receiver)
{
    resource_t *sender_res;
    tcb_t      *sender;
    tcb_t      *receiver = _receiver;
    assert(tcb_is_valid(receiver));

    switch (ep->state) {
        case EP_IDLE:
        case EP_RECV:
            if (msgtag_get_extra(tag) != 0)    // non-block receive
                task_set_retcode(receiver, EAGAIN);
            else {
                endpoint_enque(ep, receiver);
                ep->state = EP_RECV;
            }
            break;
        case EP_SEND:
            sender_res = list_first_entry(&ep->waitlist, resource_t, s_list);
            sender     = sender_res->s_owner;
            endpoint_deque(sender);
            ipcbuffer_full_transfer(receiver, sender);
            if (task_get_flags(sender) & TASK_ACT_CALL) {
                endpoint_get(ep);
                // if sender want to do a call
                sender->calledep = ep;
                list_insert(&sender->callernode, &receiver->callers);
                task_set_state(sender, TASK_BLOCKED);
                task_clear_flags(sender, TASK_ACT_CALL);

                try_promote_priority(receiver, sender);
            } else {
                task_set_retcode(sender, ESUCCESS);
                schedule_attach_ready(sender);
            }
            if (task_get_aid(receiver) == 0L) {
                task_get_ipcbuffer(receiver)->ep_sender_aid  = task_get_aid(sender);
                task_get_ipcbuffer(receiver)->ep_sender_cptr = sender->cptr;
            }
            task_set_retcode(receiver, ESUCCESS);
            if (list_is_empty(&ep->waitlist)) {
                ep->state = EP_IDLE;
            }
            break;
        default:
            task_set_retcode(receiver, EINVAL);
            break;
    }
}

void endpoint_call(endpoint_t *ep, uint64_t tag, uint64_t m0, uint64_t m1, void *_caller)
{
    tcb_t *caller = _caller;
    task_set_flags(caller, TASK_ACT_CALL);
    endpoint_send(ep, tag, m0, m1, caller);
}

int32_t endpoint_reply(endpoint_t *ep, uint64_t tag, uint64_t m0, uint64_t m1, void *_replier)
{
    tcb_t *replier = _replier;
    tcb_t *caller  = list_first_entry(&replier->callers, tcb_t, callernode);

    assert(tcb_is_valid(replier));

    if (list_is_empty(&replier->callers) || caller->calledep != ep) {
        // there's no caller saved or the endpoint is not match
        task_set_retcode(replier, EGENERAL);
        return EGENERAL;
    }

    list_remove(&caller->callernode);
    ipcbuffer_regs_transfer(caller, tag, m0, m1);
    ipcbuffer_partial_transfer(caller, replier, tag);

    // recovery the priority of replier
    recovery_priority(replier);

    task_set_retcode(caller, ESUCCESS);

    schedule_attach_ready(caller);

    task_set_retcode(replier, ESUCCESS);
    endpoint_put(ep);

    return ESUCCESS;
}

void endpoint_cancel(endpoint_t *ep)
{
    tcb_t *waiter;
    tcb_t *waiter_next;

    list_foreach_safe(waiter, waiter_next, &ep->waitlist, tcb_t, runlist)
    {
        task_clear_flags(waiter, TASK_ACT_CALL);
        list_remove(&waiter->runlist);
        schedule_attach_ready(waiter);
        task_set_retcode(waiter, ECANCELED);
    }

    ep->state = EP_DIED;
}

void endpoint_clean_callers(void *_tsk)
{
    tcb_t *tsk      = (tcb_t *)_tsk;
    tcb_t *cur      = NULL;
    tcb_t *cur_next = NULL;
    assert(tcb_is_valid(tsk));

    list_foreach_safe(cur, cur_next, &tsk->callers, tcb_t, callernode)
    {
        list_remove(&cur->callernode);
        schedule_attach_ready(cur);
        task_set_retcode(cur, ECANCELED);
        endpoint_put(cur->calledep);
    }
}